﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;


namespace Expression.Samples.Physics {

	public class BodyControl : Control, IShapeProvider {

		public static readonly DependencyProperty InitialRotationalVelocityProperty = DependencyProperty.Register("InitialRotationalVelocity", typeof(double), typeof(BodyControl), new PropertyMetadata(0.0));
		public static readonly DependencyProperty DensityProperty = DependencyProperty.Register("Density", typeof(double), typeof(BodyControl), new PropertyMetadata(1.0));
		public static readonly DependencyProperty IsStaticProperty = DependencyProperty.Register("IsStatic", typeof(bool), typeof(BodyControl), new PropertyMetadata(null));
		public static readonly DependencyProperty RestitutionProperty = DependencyProperty.Register("Restitution", typeof(double), typeof(BodyControl), new PropertyMetadata(0.1));
		public static readonly DependencyProperty FrictionProperty = DependencyProperty.Register("Friction", typeof(double), typeof(BodyControl), new PropertyMetadata(0.3));
		public static readonly DependencyProperty InitialVelocityProperty = DependencyProperty.Register("InitialVelocity", typeof(Vector), typeof(BodyControl), new PropertyMetadata(new Vector(0, 0)));

		private Entity entity;
		private PhysicsContext context;

		public BodyControl() {
			this.DefaultStyleKey = typeof(BodyControl);
			this.entity = new Entity(this);

			this.Entity.Collide += delegate(object sender, CollideEventArgs e) {
				this.OnCollide(e);
			};

			this.Entity.BoundaryViolation += delegate(object sender, EventArgs e) {
				this.OnBoundaryViolation(e);
			};
		}

		public EventHandler<CollideEventArgs> Collide;

		public FrameworkElement BodyShape {
			get {
				return this.GetTemplateChild("BodyShape") as FrameworkElement;
			}
		}

		public double Density {
			get { return (double)this.GetValue(BodyControl.DensityProperty); }
			set { this.SetValue(BodyControl.DensityProperty, value); }
		}

		public bool IsStatic {
			get { return (bool)this.GetValue(BodyControl.IsStaticProperty); }
			set { this.SetValue(BodyControl.IsStaticProperty, value); }
		}

		public double Restitution {
			get { return (double)this.GetValue(BodyControl.RestitutionProperty); }
			set { this.SetValue(BodyControl.RestitutionProperty, value); }
		}

		public double Friction {
			get { return (double)this.GetValue(BodyControl.FrictionProperty); }
			set { this.SetValue(BodyControl.FrictionProperty, value); }
		}

		public Vector InitialVelocity {
			get { return (Vector)this.GetValue(BodyControl.InitialVelocityProperty); }
			set { this.SetValue(BodyControl.InitialVelocityProperty, value); }
		}

		public double InitialRotationalVelocity {
			get { return (double)this.GetValue(BodyControl.InitialRotationalVelocityProperty); }
			set { this.SetValue(BodyControl.InitialRotationalVelocityProperty, value); }
		}


		public Entity Entity {
			get { return this.entity; }
		}

		public void RemoveEntity() {
			this.Entity.Simulator.RemoveEntity(this.Entity);
		}

		public override void OnApplyTemplate() {
			base.OnApplyTemplate();

			BodyParameters bodyParams = new BodyParameters() {
				Density = this.Density,
				Friction = this.Friction,
				IsStatic = this.IsStatic,
				Restitution = this.Restitution,
				InitialVelocity = this.InitialVelocity,
				InitialRotationalVelocity = this.InitialRotationalVelocity,
			};

			if (!DesignerProperties.GetIsInDesignMode(this)) {

				this.Dispatcher.BeginInvoke(delegate {

					this.Simulator.AddEntity(this.Entity, bodyParams);
				});
			}
		}

		protected virtual void OnCollide(CollideEventArgs e) {
			if (this.Collide != null)
				this.Collide(this, e);
		}

		protected virtual void OnBoundaryViolation(EventArgs e) {
		}

		protected Simulator Simulator {
			get {
				if (this.context == null) {
					this.TryGetContext();
				}
				return this.context.Simulator;
			}
		}

		private void TryGetContext() {
			PhysicsContext context = PhysicsContext.FindContext(this);
			if (context == null) {
				context = new PhysicsContext();
				PhysicsContext.SetPhysicsContext(Application.Current.RootVisual, context);
			}

			this.context = context;
		}


	}
}
